package com.persia.dao;

import com.persia.tool.CollectionTool;
import com.persia.tool.InputValidator;
import org.hibernate.*;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;

import javax.annotation.Resource;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * hibernate 4 基础泛型dao
 * 如果你发现所需的方法，这里没有
 * 请考虑：1、是作者故意不提供，即不推荐的，还是2、作者漏掉的
 * 若需要添加，请测试性能后添加
 * 另外，请注意，hibernate不适合的场景：
 * （1）复杂查询/大批量数据导出
 * （2）批量修改/删除
 * （3）实体关系过于复杂
 * @author caibosi
 * @created 2014-02-03
 */
public class GenericDao<T>{

    /**
     * 泛型dao要用到的class
     */
    protected Class<T> clazz;

    /**
     * Spring4+Hibernate4中不再支持HibernaterTemplate，只能使用原生的session接口来操作
     */
    @Resource
    protected SessionFactory sessionFactory;

    /**
     * 使用getCurrentSession
     * 1、将创建的session绑定到当前线程中
     * 2、session在事务commit或rollback的时候会自动关闭
     * @author caibosi
     * @created 2014-02-10
     * @return
     */
    protected final Session getCurrentSession(){
        return sessionFactory.getCurrentSession();
    }

    /**
     * 构造器
     * 自动获取class
     */
    public GenericDao(){
        Class clazz = getClass();

        while (clazz != Object.class) {
            Type t = clazz.getGenericSuperclass();
            if (t instanceof ParameterizedType) {
                Type[] args = ((ParameterizedType) t).getActualTypeArguments();
                if (args[0] instanceof Class) {
                    this.clazz = (Class<T>) args[0];
                    break;
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

    /******************************************
     * 查询、统计方法
     /******************************************/

    /**
     * 查询一个
     * @param entityId
     * @return
     */
    public T findOne(String entityId) {
        InputValidator.checkNotEmpty(entityId,"entityId is empty");
        return (T) getCurrentSession().get(clazz, entityId);
    }

    /**
     * 通过条件查询
     * criteria的list方法会填充一二级缓存
     * 大批量查询时要注意
     * @param dc
     * @return
     */
    public List<T> findByCriteria(DetachedCriteria dc) {
        InputValidator.checkNotNull(dc,"DetachedCriteria is null");
        Criteria executableCriteria = dc.getExecutableCriteria(getCurrentSession());
        return executableCriteria.list();
    }

    /**
     * 通过hql来查询
     * @param hql
     * @param params
     * @return
     */
    public List<T> findByHql(String hql,Map<String,String> params){
        InputValidator.checkNotEmpty(hql,"hql is empty");
        Query query = getCurrentSession().createQuery(hql);
        if(CollectionTool.isNotEmpty(params)){
            for(Map.Entry<String,String> entry : params.entrySet()){
                String key = entry.getKey();
                String value = entry.getValue();
                //需要调试下其他数据类型是否可以统一用string来表达
                query.setParameter(key,value);
            }
        }
        return query.list();
    }

    /**
     * 通过sql查询
     * @param sql
     * @return
     */
    public List<Object[]> findBySql(String sql){
        InputValidator.checkNotEmpty(sql,"sql is empty");
        SQLQuery sqlQuery = getCurrentSession().createSQLQuery(sql);
        return sqlQuery.list();
    }

    /**
     * 通过sql和page查询
     * @param sql
     * @param page
     * @return
     */
    public List<Object[]> findBySqlPage(String sql,Page page){
        InputValidator.checkNotEmpty(sql,"sql is empty");
        InputValidator.checkNotNull(page,"page is null");
        SQLQuery sqlQuery = getCurrentSession().createSQLQuery(sql);
        sqlQuery.setFirstResult(page.getStart());
        sqlQuery.setMaxResults(page.getPerPageSize());
        return sqlQuery.list();
    }

    /**
     * 分页查询
     * 这里跟hibernate的criteria 耦合了
     * 严格的话，不推荐这样做
     * @param dc
     * @param page
     * @return
     */
    public List<T> findByCriteriaPage(DetachedCriteria dc, Page page) {
        //参数校验
        InputValidator.checkNotNull(dc,"DetachedCriteria is null");
        InputValidator.checkNotNull(page,"page is null");
        InputValidator.checkTrue(page.getStart() != null && page.getStart().intValue() >= 0,"page start is invalid");
        InputValidator.checkTrue(page.getPerPageSize()!=null && page.getPerPageSize()>0,"page size is invalid");

        //执行查询
        Criteria executableCriteria = dc.getExecutableCriteria(getCurrentSession());
        executableCriteria.setFirstResult(page.getStart());
        executableCriteria.setMaxResults(page.getPerPageSize());
//        executableCriteria.setTimeout();
        return executableCriteria.list();
    }

    /**
     * 统计个数
     * 建议仅限统计分页用
     * @param dc
     * @return
     */
    public long findCount(DetachedCriteria dc) {
        InputValidator.checkNotNull(dc,"DetachedCriteria is null");
        Criteria criteria = dc.getExecutableCriteria(getCurrentSession());
        criteria.setFirstResult(0);
        Long count = (Long)criteria.setProjection(Projections.rowCount()).uniqueResult();
        return count.longValue();
    }

    /**
     * 通过sql查询总数
     * @param countSql
     * @return
     */
    public long findCount(String countSql){
        InputValidator.checkNotEmpty(countSql,"sql is empty");
        SQLQuery sqlQuery = getCurrentSession().createSQLQuery(countSql);
        return (Long) sqlQuery.uniqueResult();
    }

    public void findCountPage(String countSql,Page page){
        InputValidator.checkNotEmpty(countSql,"sql is empty");
        InputValidator.checkNotNull(page,"page is null");
        SQLQuery sqlQuery = getCurrentSession().createSQLQuery(countSql);
        Long count = (Long) sqlQuery.uniqueResult();
        page.setTotalCount(count);
    }

    /******************************************
     * 创建、更新、删除操作
     /******************************************/

    /**
     * 创建实体
     * @param entity
     */
    public void create(final T entity) {
        InputValidator.checkNotNull(entity,"entity is null");
        getCurrentSession().save(entity);
    }

    /**
     * 更新实体
     * @param entity
     */
    public void update(T entity) {
        InputValidator.checkNotNull(entity,"entity is null");
        getCurrentSession().update(entity);
    }



    /**
     * 跟新部分字段
     * 建议同步数据时用
     * @param updateQuery
     */
    void partialUpdate(final Query updateQuery){

    }

    /**
     * 删除实体
     * @param entity
     */
    public void delete(T entity) {
        InputValidator.checkNotNull(entity,"entity is null");
        getCurrentSession().delete(entity);
    }


    /**
     * 通过id删除实体
     * @param entityId
     */
    public void deleteById(String entityId) {
        InputValidator.checkNotEmpty(entityId,"entityid is empty");
        final T entity = findOne(entityId);
        InputValidator.checkState(entity != null,"entity find by id is null");
        delete(entity);
    }

    /******************************************
     * 批量方法，同时更新缓存，数据量大不建议用
     /******************************************/

    /**
     * 创建几个实体
     * 仅限几个，同时更新缓存
     * @param entities
     */
    public void create(final Collection<T> entities) {
        InputValidator.checkTrue(CollectionTool.isEmpty(entities) == true,"collection is empty");
        for(T obj : entities){
            getCurrentSession().save(obj);
        }
    }

    /**
     * 更新几个实体
     * update方法会更新一二级缓存
     * 大批量数据更新不建议用
     * @param entities
     */
    public void update(final Collection<T> entities){
        InputValidator.checkTrue(CollectionTool.isEmpty(entities) == true,"collection is empty");
        for(T entity : entities){
            update(entity);
        }
    }

    /**
     * 删除几个实体
     * 同时会更新缓存
     * @param entities
     */
    public void delete(final Collection<T> entities){
        InputValidator.checkTrue(CollectionTool.isEmpty(entities) == true,"collection is empty");
        for(T entity : entities){
            delete(entity);
        }
    }

    /******************************************
     * 批量方法，不更新缓存
     /******************************************/

    /**
     * 批量导入，清除缓存
     * @param entities
     */
    public void batchCreate(final Collection<T> entities){
        InputValidator.checkTrue(CollectionTool.isEmpty(entities) == true,"collection is empty");
        int i = 0 ;
        for(T entity : entities){
            getCurrentSession().save(entity);
            i++;
            //flush，同步缓存，执行sql，clear则清空缓存
            if(i % 20 == 0){
                getCurrentSession().flush();
                getCurrentSession().clear();
            }
        }
    }

    /**
     * 批量更新，不建议先加载再更新
     * 采用stateless session 或者 ScrollableResults 来更新
     */

//    public void batchUpdate(final Collection<T> entities);
//    public void batchDelete(final Collection<T> entities);


}
